package com.backapp.app;

//
//TODO: Exit if data not available
//TODO: Exit if Dropbox not installed
//

import java.io.*;
import java.util.*;

import com.dropbox.client2.*;
import com.dropbox.client2.DropboxAPI.Entry;
import com.dropbox.client2.android.*;
import com.dropbox.client2.session.*;
import android.app.*;
import android.content.*;
import android.content.SharedPreferences.Editor;
import android.net.*;
import android.os.*;
import android.view.*;
import android.widget.*;
import android.widget.AdapterView.OnItemClickListener;

/**
 * User activity for the app.
 */
public class Main extends Activity implements DBListingListener,
		AppListingCompletedListener, DBDownloadListener, 
		UploadListener, ExtractionCompletedListener
{
	private Button mSuperuserButton;
	private Button mUserAppsButton;
	private Button mListBackupsButton;
	private ProgressBar mProgressBar;
	
	private RelativeLayout mBaseLayout;

	private ListView mListView;

	private DropboxAPI<AndroidAuthSession> mDBApi;
	private boolean mDBLoggedIn;

	private List<AppDetails> mAppDetails;
	private ArrayAdapter<String> mListAdapter;

	private boolean mDownLoad = false;
	private List<Entry> mDownloadableEntries;

	private String packageGZ;
	
	private View.OnClickListener mButtonListener = new View.OnClickListener()
	{
		@Override
		public void onClick(View v)
		{
			if (!isDropBoxLoggedIn())
			{
				Utils.toastMessage(getApplicationContext(), "App not connected to Dropbox");
				
				return;
			}
			
			switch (v.getId())
			{
			case R.id.superuserButton:

				//
				// Gets super user permissions for the app.
				//

				getSudo();
				break;

			case R.id.userAppsButton:

				//
				// Lists all available apps to upload.
				//

				getAppList();
				break;

			case R.id.listBackupsButton:
				listBackups();
				break;

			default:

				//
				// Hopefully, never end up here.
				// TODO: Suitable error handling. Screaming
				// obscenities is not a good practice.
				//

				Utils.toastMessage(getApplicationContext(), "WTF");
			}
		}
	};

	private OnItemClickListener mItemClickListener = new OnItemClickListener()
	{
		@Override
		public void 
		onItemClick(
			AdapterView<?> arg0, 
			View arg1, 
			int arg2,
			long arg3
			)
		{
			if (!isDropBoxLoggedIn())
			{
				Utils.toastMessage(getApplicationContext(), "Dropbix login needed");
				return;
			}
			
			try
			{
				if (mDownLoad)
				{
					String appName;
					DownloadFiles downloader;

					Utils.logInfo("Item Clicked: " + mListAdapter.getItem(arg2));

					appName = mListAdapter.getItem(arg2);
					downloader = new DownloadFiles(mDBApi, Main.this);
					showProgressBar();
					downloader.execute(getAppEntries(appName));
				} 
				else
				{
					Utils.logInfo("Item Clicked: "
							+ mAppDetails.get(arg2).getName());
					
					Utils.copyAppData(mAppDetails.get(arg2));
					uploadBackappDir();
				}
			} 
			catch (Exception e)
			{
				Utils.logError("copyAppData err: " + e.toString());
			}
		}
	};

	/**
	 * Called when the activity is first created.
	 */
	@Override
	public void 
	onCreate(
		Bundle savedInstanceState
		)
	{
		super.onCreate(savedInstanceState);

		//
		// Set UI layout.
		//

		setContentView(R.layout.newlayout);

		init();
	}

	private void 
	init()
	{
		uiInit();

		//
		// TODO: Check if I should do this here?
		//

		//
		// Init dropbox session, api etc.
		//

		dropboxInit();

		mListAdapter = new ArrayAdapter<String>(getApplicationContext(),
				android.R.layout.simple_list_item_1, android.R.id.text1);
	}

	private void 
	startAppLister()
	{
		AppLister appLister = new AppLister(getApplicationContext(), this);
		appLister.execute();
		showProgressBar();
	}

	/**
	 * Override base class function.
	 */
	@Override
	protected void 
	onResume()
	{
		super.onResume();

		//
		// Copied as is from Dropbox sdk tutorial.
		//

		if (mDBApi.getSession().authenticationSuccessful())
		{
			try
			{
				//
				// MANDATORY call to complete auth.
				// Sets the access token on the session
				//

				mDBApi.getSession().finishAuthentication();

				AccessTokenPair tokens = mDBApi.getSession()
						.getAccessTokenPair();

				//
				// Provide your own storeKeys to persist the access token pair
				// A typical way to store tokens is using SharedPreferences
				//

				storeKeys(tokens.key, tokens.secret);
			} catch (IllegalStateException e)
			{
				Utils.logError("Error authenticating" + e.getMessage());
			}
		}
	}

	/**
	 * Initializes UI components.
	 */
	private void 
	uiInit()
	{
		mSuperuserButton = (Button) findViewById(R.id.superuserButton);
		mSuperuserButton.setOnClickListener(mButtonListener);

		mUserAppsButton = (Button) findViewById(R.id.userAppsButton);
		mUserAppsButton.setOnClickListener(mButtonListener);
		mBaseLayout = (RelativeLayout) findViewById(R.id.rootLayout);

		mListView = (ListView) findViewById(R.id.listView);
		mListView.setOnItemClickListener(mItemClickListener);

		mListBackupsButton = (Button) findViewById(R.id.listBackupsButton);
		mListBackupsButton.setOnClickListener(mButtonListener);
		
		mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
	}

	/**
	 * Initialises dropbox session, api etc.
	 */
	private void 
	dropboxInit()
	{
		AndroidAuthSession session = buildSession();
		mDBApi = new DropboxAPI<AndroidAuthSession>(session);

		mDBLoggedIn = mDBApi.getSession().isLinked();

		//
		// If the user hasn't logged in yet, login now.
		//

		if (!mDBLoggedIn)
		{
			mDBApi.getSession().startAuthentication(Main.this);
		}
	}

	private boolean
	isDropBoxLoggedIn()
	{
		return mDBApi.getSession().isLinked();
	}
	
	/**
	 * Gets sudo privileges. Source:
	 * http://www.stealthcopter.com/blog/2010/01/android
	 * -requesting-root-access-in-your-app/
	 */
	private void 
	getSudo()
	{
		java.lang.Process proc;

		try
		{
			DataOutputStream os;

			//
			// To get root privileges.
			//

			proc = Runtime.getRuntime().exec("su");

			//
			// Try writing a file in a root-only location.
			//

			os = new DataOutputStream(proc.getOutputStream());
			os.writeBytes("echo \"Do I have root?\" >/system/sd/temporary.txt\n");

			//
			// Closes the terminal.
			//

			os.writeBytes("exit\n");
			os.flush();

			try
			{
				proc.waitFor();

				if (proc.exitValue() != 255)
				{
					Utils.toastMessage(getApplicationContext(), "Sucess!!!");
				} else
				{
					//
					// TODO: better error handling.
					//

					Utils.toastMessage(getApplicationContext(),
							"WTF!! Failue!!");
				}
			} catch (InterruptedException e)
			{
				//
				// TODO: better error handling.
				//

				Utils.toastMessage(getApplicationContext(), "Interrupted catch");
			}
		} catch (IOException e)
		{
			//
			// TODO: better error handling.
			//

			Utils.toastMessage(getApplicationContext(), "External catch");
		}
	}

	private void 
	showListView()
	{
		List<String> appNames;

		appNames = new ArrayList<String>();

		for (AppDetails appDetails : mAppDetails)
		{
			appNames.add(appDetails.getName());
		}

		mListAdapter.clear();
		mListAdapter.addAll(appNames);
		mListAdapter.notifyDataSetChanged();
		mListView.setAdapter(mListAdapter);
		mListView.setVisibility(View.VISIBLE);

		mDownLoad = false;
	}

	/**
	 * Uploads all files in the '/backapp' directory to the dropbox folder.
	 */
	private void 
	uploadBackappDir()
	{
		File backAppDir;

		try
		{
			backAppDir = new File(Environment.getExternalStorageDirectory()
					.getCanonicalPath() + "/backapp/");

			if (!backAppDir.isDirectory())
			{
				Utils.logError("backapp is not a directory");
			}

			showProgressBar();
			
			for (File file : backAppDir.listFiles())
			{
				Utils.logInfo("Uploading: " + file.getName());
				Utils.toastMessage(getApplicationContext(), "Uploading " + file.getName());
				uploadFile(file);
			}
		} 
		catch (IOException e)
		{
			Utils.logError("Error openeing backapp dir: " + e.getMessage());
		}
	}

	/**
	 * Uploads a file to the dropbox folder.
	 * 
	 * @param apkFile
	 */
	private void 
	uploadFile(
		File apkFile
		)
	{
		UploadFile uFile = null;

		uFile = new UploadFile(apkFile.getAbsolutePath(), mDBApi, this);
		Utils.logInfo("Uploading file");
		uFile.execute();
	}

	private void 
	listBackups()
	{
		DBLister dbLister = null;

		dbLister = new DBLister(mDBApi, this);
		Utils.logInfo("Getting metadata");
		showProgressBar();
		dbLister.execute();
	}

	/**
	 * Shows keeping the access keys returned from Trusted Authenticator in a
	 * local store, rather than storing user name & password, and
	 * re-authenticating each time (which is not to be done, ever).
	 */
	private void 
	storeKeys(
		String key, 
		String secret
		)
	{
		// Save the access key for later
		SharedPreferences prefs = getSharedPreferences(Utils.PREFS_NAME, 0);
		Editor edit = prefs.edit();
		edit.putString(Utils.PREF_NAME_KEY, key);
		edit.putString(Utils.PREF_NAME_SECRET, secret);
		edit.commit();
	}

	/**
	 * Shows keeping the access keys returned from Trusted Authenticator in a
	 * local store, rather than storing user name & password, and
	 * re-authenticating each time (which is not to be done, ever).
	 * 
	 * @return Array of [access_key, access_secret], or null if none stored
	 */
	private String[] 
	getKeys()
	{
		SharedPreferences prefs = getSharedPreferences(Utils.PREFS_NAME, 0);
		String key = prefs.getString(Utils.PREF_NAME_KEY, null);
		String secret = prefs.getString(Utils.PREF_NAME_SECRET, null);

		if (key != null && secret != null)
		{
			String[] ret = new String[2];
			ret[0] = key;
			ret[1] = secret;

			return ret;
		} 
		else
		{
			return null;
		}
	}

	private AndroidAuthSession 
	buildSession()
	{
		AppKeyPair appKeyPair = new AppKeyPair(Utils.DROPBOX_KEY,
				Utils.DROPBOX_SECRET);
		AndroidAuthSession session;

		String[] stored = getKeys();

		if (stored != null)
		{
			AccessTokenPair accessToken = new AccessTokenPair(stored[0],
					stored[1]);
			session = new AndroidAuthSession(appKeyPair, Utils.ACCESS_TYPE,
					accessToken);
		} else
		{
			session = new AndroidAuthSession(appKeyPair, Utils.ACCESS_TYPE);
		}

		return session;
	}

	private void 
	getAppList()
	{
		startAppLister();
	}

	@Override
	public void 
	metaDataAvailable(
		List<Entry> entries
		)
	{
		List<String> fileNames;

		fileNames = new ArrayList<String>();

		for (Entry entry : entries)
		{
			if (entry.fileName().endsWith(".apk"))
			{
				fileNames.add(entry.fileName().split("\\.")[0]);
			}
		}
		
		hideProgressBar();

		mListAdapter.clear();
		mListAdapter.addAll(fileNames);
		mListAdapter.notifyDataSetChanged();
		mListView.setAdapter(mListAdapter);
		mListView.setVisibility(View.VISIBLE);

		mDownLoad = true;
		mDownloadableEntries = entries;
	}

	@Override
	public void 
	onListingCompleted(
		List<AppDetails> appDetails
		)
	{
		mAppDetails = appDetails;
		hideProgressBar();
		showListView();
		Utils.logInfo("Listing completed");
	}

	private List<Entry> 
	getAppEntries(
		String appName
		)
	{
		List<Entry> entries = new ArrayList<DropboxAPI.Entry>();
		
		for (Entry entry : mDownloadableEntries)
		{
			if (entry.fileName().startsWith(appName))
			{
				entries.add(entry);
			}
		}

		return entries;
	}

	@Override
	public void 
	onDBDownLoadCompleted(
		List<String> files
		)
	{
		
		Utils.logInfo("onDBDownLoadCompleted: " + files.size());

		hideProgressBar();

		try
		{
			if (files.get(0).endsWith(".apk"))
			{
				packageGZ = files.get(1);
				install(files.get(0));
			} 
			else
			{
				packageGZ = files.get(0);
				install(files.get(1));
			}
		} 
		catch (Exception e)
		{
			//
			// TODO Error handling
			//

			Utils.logError("Install/Unpack error: " + e.getMessage());
			finish();
		}
	}

	@Override
	protected void 
	onActivityResult(
		int requestCode, 
		int resultCode, 
		Intent data
		)
	{
		// TODO Auto-generated method stub
		super.onActivityResult(requestCode, resultCode, data);
		
		try
		{
			Utils.extractTarPackage(packageGZ);
		}
		catch (Exception e)
		{
			Utils.logInfo("Error while unpacking: " + e.getMessage());
			finish();
		}
	}

	private void 
	install(
		String apk
		)
	{
		Intent intent = new Intent(Intent.ACTION_VIEW);

		intent.setDataAndType(Uri.fromFile(new File(apk)),
				"application/vnd.android.package-archive");

		startActivityForResult(intent, 0);
	}

	@Override
	public void 
	onUploadCompleted()
	{
		hideProgressBar();
	}
	
	private void
	showProgressBar()
	{
		mProgressBar.setVisibility(View.VISIBLE);
	}
	
	private void
	hideProgressBar()
	{
		mProgressBar.setVisibility(View.GONE);
	}

	@Override
	public void 
	onExtractionCompleted(
		List<String> files
		)
	{
		String apk = null;
		
		for (String fileName : files)
		{
			if (fileName.endsWith(".apk"))
			{
				apk = fileName;
				break;
			}
		}
		
		// install(apk);
		
		try
		{
			for (String fileName : files)
			{
				if (!fileName.equals(apk) && fileName.endsWith(".gz"))
				{
					Utils.extractDataDir(fileName);
				}
			}
		}
		catch (Exception e)
		{
			Utils.logError("Extraction error while extracting data: " + e.getMessage());
		}
	}
}
